// 6.7 函数指针
/**
 * 函数指针和指针函数的概念与区别：
 * 函数指针，是个指针，它指向一个函数而非一个对象。
 * 指针函数，是个函数，它的返回值是个指针。
 */
#include <iostream>
#include <initializer_list>
#include <iterator>
#include <vector>
#include <cassert> // assert宏定义在cassert头文件中
using std::begin, std::end;
using std::cerr, std::clog;
using std::cin, std::cout, std::endl;
using std::initializer_list;
using std::string;
using std::vector;

// 函数指针
bool lengthCompare(const string &, const string &); // 定义lengthCompare函数，用于比较两个string的长度
bool (*pf)(const string &, const string &);         // 定义一个函数指针
// 函数指针指向的函数，由函数指针的形参和返回值决定

// 使用函数指针
// pf = lengthCompare;  // 不能定义在函数体外，函数体外只可以声明和初始化，不能够赋值
// pf = &lengthCompare; // 不能定义在函数体外，函数体外只可以声明和初始化，不能够赋值
bool (*pf1)(const string &, const string &) = lengthCompare;  // 初始化pf1
bool (*pf2)(const string &, const string &) = &lengthCompare; // 初始化pf2
// 此外，我们还能直接使用指向函数的指针调用该函数，无须提前解引用指针
bool b1 = pf1("hello", "world");           // 调用lengthCompare函数
bool b2 = (*pf)("hello", "world");         // 一个等价调用
bool b3 = lengthCompare("hello", "world"); // 另一个等价调用

// 函数指针形参
// 和数组类似（参见6.2.4节，第193页），虽然不能定义函数类型的形参，但是形参可以是指向函数的指针。
// 此时，形参看起来是函数类型，实际上却是当成指针使用
void useBigger(const string &s1, const string &s2, bool pf(const string &, const string &));    // 隐式，自动转换成指向函数的指针
void useBigger(const string &s1, const string &s2, bool (*pf)(const string &, const string &)); // 显示将形参定义成函数指针
// 我们可以直接把函数作为实参使用，此时它会自动转换成指针，例如：
// useBigger("hello","world",lengthCompare);

// 使用类型别名简化函数指针的使用
// 直接使用函数指针类型显得冗长而烦琐。类型别名（参见2.5.1节，第60页）和decltype（参见2.5.3节，第62页）能让我们简化使用了函数指针的代码
// Func和Func2是函数类型
typedef bool Func(const string &, const string &);
typedef decltype(lengthCompare) Func2; // Func2等价于Func
// FuncP和FuncP2是指向函数的指针
typedef bool (*FuncP)(const string &, const string &);
typedef decltype(lengthCompare) *FuncP2; // FuncP2等价于FuncP
// 使用别名的函数指针
void userBigger(const string &s1, const string &s2, FuncP);
void userBigger(const string &s1, const string &s2, FuncP2); // 等价于上一行代码

// 返回指向函数的指针
// 和数组类似（参见6.3.3节，第205页），虽然不能返回一个函数，但是能返回指向函数类型的指针。
// 然而，我们必须把返回类型写成指针形式，编译器不会自动地将函数返回类型当成对应的指针类型处理。
using F = int(int *, int);      // F是函数类型，不是指针
using PF = int (*)(int *, int); // PF是指针类型
// 必须时刻注意的是，和函数类型的形参不一样，返回类型不会自动地转换成指针。我们必须显式地将返回类型指定为指针
PF f1(string); // 正确，PF是指向函数的指针，f1返回指向函数的指针
// F f1(string); // 错误，F是函数类型，f1不能返回一个函数
F *f1(string); // 正确，显示地指向返回类型是指向函数F的指针
// 不用类型别名的方式定义f1
int (*f1(string))(int *, int); // 从里向外读：f1有形参列表说明是个函数，f1前面有*说明返回指针，指针本身也包含形参，因此指针指向函数，函数的返回类型是int
// 出于完整性的考虑，有必要提醒读者我们还可以使用尾置返回类型的方式（参见6.3.3节，第206页）声明一个返回函数指针的函数
auto f1(string) -> int (*)(int *, int); // 尾置返回类型

// 将auto和decltype用于函数指针类型
// 牢记当我们将decltype作用于某个函数时，它返回函数类型而非指针类型。因此，我们显式地加上*以表明我们需要返回指针，而非函数本身。
string::size_type sumLength(const string &, const string &);
string::size_type largerLength(const string &, const string &);
decltype(sumLength) *getFunc(const string &); // 根据其形参的取值，getFunc函数返回指向sumLength或largerLenth的指针

int main(int argc, char const *argv[])
{
    pf = lengthCompare;
    pf = &lengthCompare;

    useBigger("hello", "world", lengthCompare); // 我们可以直接把函数作为实参使用，此时它会自动转换成指针

    return 0;
}
